Skip to content

feat: chat transcript mr2a html export - #10076

Open
water-in-stone wants to merge 33 commits into
QwenLM:mainfrom
water-in-stone:codex/feat-chat-transcript-mr2a-html-export
Open

feat: chat transcript mr2a html export#10076
water-in-stone wants to merge 33 commits into
QwenLM:mainfrom
water-in-stone:codex/feat-chat-transcript-mr2a-html-export

Conversation

@water-in-stone

Copy link
Copy Markdown
Collaborator

What this PR does

This PR delivers MR2A of the shared chat transcript design: a production HTML Export pipeline without migrating the VS Code live timeline.

  • Projects saved chat records through the existing canonical transcript semantics into a versioned, closed ExportTranscriptDocumentV1 with per-kind allowlists, opaque document IDs, typed tool previews/results, completeness diagnostics, and explicit resource budgets.
  • Renders the safe document through Web Shell document mode with full-document search, selection, copy, and print behavior; document-only Mermaid limits, safe fallbacks, and remote-resource restrictions do not change interactive or readonly runtime behavior.
  • Routes CLI, Web API, VS Code /export html, and the integration runner through the same version-bound product renderer while preserving the legacy HTML call when original records are unavailable.
  • Preserves existing interactive/readonly raw tool semantics, including complete write_file Turn Output diffs; document projection never falls back to raw payloads.
  • Keeps the VS Code live timeline on its existing legacy MessageList. MR2A adds no shared renderer dependency, transcript feature flag, live update forwarding, production adapter, or host-action seam; direct-daemon and ACP identity remain candidate evidence only, with selectedVscodePath: null and overall: "fail" until MR2B.

Why it's needed

The existing hosts have separate transcript and export paths, which risks duplicated replay/rendering behavior and inconsistent portable output. HTML export also needs a one-way security boundary so raw tool payloads, credentials, absolute user paths, session identity, unsafe resources, and unbounded rendering work cannot enter a shareable document. MR2A establishes that bounded document path independently from the higher-risk VS Code live timeline migration, allowing the two changes to be reviewed, rolled out, and reverted separately.

Reviewer Test Plan

How to verify

  1. Run npm run build and npm run typecheck; expect the SDK, CLI, Web Shell library, embedded document renderer, and VS Code companion export consumer to build and type-check while VS Code continues to use its legacy live timeline.
  2. Run npx vitest run --root ./integration-tests ./chat-transcript-contract.test.ts --retry=0; expect fixture hashes, the production schema, raw-free document semantics, and both candidate identity projections to pass while selectedVscodePath remains null and the overall gate remains fail.
  3. Run the focused SDK, CLI Export, Web Shell document-mode, and VS Code session-export tests; expect typed preview/result rendering in document mode, unchanged interactive/readonly raw behavior, complete write_file Turn Output diffs, and VS Code /export html to pass original records into the shared document pipeline.
  4. Generate HTML through the CLI, Web API, VS Code /export html, and the integration runner; expect the same version-bound, self-contained renderer and a safe read-only failure for incompatible schema or renderer versions.
  5. Run the product HTML browser gate with Chromium; expect maximum-budget open/search/copy/print behavior, zero unexpected network requests, zero CSP violations, no canary leakage, and safe degradation for remote images, unsafe Markdown/HTML, Mermaid, code, and diff failures.
  6. Regenerate VS Code third-party notices; expect no transcript-related dependency or NOTICES.txt delta in MR2A because the live Web Shell timeline dependency is deferred to MR2B.

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Risk & Scope

  • Main risk or tradeoff: The document projector and renderer are a security boundary; under-sanitization could leak sensitive data or trigger network activity, while over-sanitization could remove valid transcript content. Shared renderer changes must also remain isolated from interactive and readonly modes.
  • Not validated / out of scope: VS Code live transcript migration, product-path selection, scope/generation admission, reconnect and late-update handling, host-action parity, VSIX and installed-artifact packaging, Desktop installed-artifact smoke, OpenWork/Craft Electron, and OS-specific validation are deferred or not provided.
  • Breaking changes / migration notes: No live VS Code timeline migration is included; the legacy MessageList remains authoritative. Interactive/readonly raw semantics remain the compatibility baseline, document-only safe projection does not flow back into live sessions, and the migration gate intentionally remains overall: "fail" with selectedVscodePath: null.

Linked Issues

Linked #9387

中文说明

What this PR does

本 PR 交付共享聊天 transcript 设计的 MR2A:落地生产级 HTML Export pipeline,但不迁移 VS Code live timeline。

  • 将已保存的聊天记录通过现有 canonical transcript 语义投影为版本化、封闭的 ExportTranscriptDocumentV1,包含逐 kind allowlist、文档内 opaque ID、typed tool preview/result、完整性 diagnostic 和显式资源预算。
  • 通过 Web Shell document mode 渲染安全文档,支持全文搜索、选择、复制和打印;仅 document mode 生效的 Mermaid 限制、安全 fallback 和远程资源约束不会改变 interactive 或 readonly runtime 行为。
  • 让 CLI、Web API、VS Code /export html 和 integration runner 复用同一个版本绑定的产品 renderer;无法取得原始 records 时仍保留 legacy HTML 调用兼容性。
  • 保持现有 interactive/readonly raw 工具语义,包括完整的 write_file Turn Output diff;document projection 永不回退 raw payload。
  • VS Code live timeline 继续使用现有 legacy MessageList。MR2A 不新增 shared renderer 依赖、transcript feature flag、live update 转发、生产 adapter 或 host-action seam;direct-daemon 与 ACP identity 仅作为候选证据保留,在 MR2B 前维持 selectedVscodePath: nulloverall: "fail"

Why it's needed

现有宿主拥有不同的 transcript 与 export 路径,存在重复 replay/rendering 行为和可移植输出不一致的风险。HTML export 还需要一个单向安全边界,防止 raw tool payload、credential、绝对用户路径、session identity、危险资源和无界渲染工作进入可分享文档。MR2A 将该有界 document path 与风险更高的 VS Code live timeline 迁移分离,使两者可以独立评审、发布和回滚。

Reviewer Test Plan

How to verify

  1. 运行 npm run buildnpm run typecheck;预期 SDK、CLI、Web Shell library、内嵌 document renderer 和 VS Code companion export consumer 均可构建并通过类型检查,同时 VS Code 继续使用 legacy live timeline。
  2. 运行 npx vitest run --root ./integration-tests ./chat-transcript-contract.test.ts --retry=0;预期 fixture hashes、生产 schema、raw-free document semantics 和两条 candidate identity projection 均通过,同时 selectedVscodePath 保持 null、overall gate 保持 fail
  3. 运行聚焦的 SDK、CLI Export、Web Shell document-mode 和 VS Code session-export 测试;预期 document mode 使用 typed preview/result 渲染,interactive/readonly raw 行为不变,write_file Turn Output diff 保持完整,并且 VS Code /export html 将原始 records 传入共享 document pipeline。
  4. 分别通过 CLI、Web API、VS Code /export html 和 integration runner 生成 HTML;预期使用同一个版本绑定、自包含的 renderer,并在 schema 或 renderer version 不兼容时安全地只读失败。
  5. 使用 Chromium 运行产品 HTML browser gate;预期最大预算文档可执行 open/search/copy/print,unexpected network requests 为零、CSP violations 为零、无 canary 泄漏,并对远程图片、危险 Markdown/HTML、Mermaid、code 和 diff 失败进行安全降级。
  6. 重新生成 VS Code third-party notices;预期 MR2A 不引入 transcript 相关依赖或 NOTICES.txt diff,因为 live Web Shell timeline 依赖已延后到 MR2B。

Evidence (Before & After)

未提供 screenshot 或 recording。输入文档记录了可重复 fixtures、SHA-256 锁、capability matrix、聚焦的 SDK/Core/CLI/Web Shell/VS Code export 覆盖、direct-daemon/ACP candidate checks、产品 HTML build assertions,以及覆盖最大文档、主动 CSP enforcement、零意外网络请求、搜索、复制、打印、远程资源降级和时间戳归一化的 Chromium browser gate。

Tested on

OS Status
🍏 macOS ⚠️ not tested — 输入文档未注明操作系统
🪟 Windows ⚠️ not tested — 输入文档未注明操作系统
🐧 Linux ⚠️ not tested — 输入文档未注明操作系统

Environment (optional)

N/A — 输入文档未注明 runtime environment 或 sandbox。

Risk & Scope

  • Main risk or tradeoff: document projector 和 renderer 是安全边界;脱敏不足可能泄漏敏感数据或触发网络活动,过度脱敏则可能删除有效 transcript 内容。共享 renderer 改动还必须与 interactive 和 readonly mode 保持隔离。
  • Not validated / out of scope: VS Code live transcript migration、产品路径选择、scope/generation admission、reconnect 与迟到 update 处理、host-action parity、VSIX 与 installed-artifact packaging、Desktop installed-artifact smoke、OpenWork/Craft Electron 和 OS-specific validation 均已延后或未提供证据。
  • Breaking changes / migration notes: 本 PR 不包含 VS Code live timeline 迁移;legacy MessageList 继续作为权威路径。interactive/readonly raw 语义仍是兼容性基线,document-only safe projection 不会回流 live session,并且 migration gate 有意维持 overall: "fail"selectedVscodePath: null

Linked Issues

#9387

N/A


heyang.why added 17 commits August 20, 2026 15:26
Establish a shared transcript model and document-mode projection so Web
Shell, VS Code, and HTML export can consume the same stable conversation
semantics without changing interactive rendering.

- Preserve daemon and ACP segment identity across replay and normalization
- Add export-safe previews and a versioned transcript document builder
- Add document-mode Web Shell rendering with bounded Mermaid processing
- Lock direct-daemon and ACP behavior with contract fixtures
- Cover render and export equivalence in integration tests
Move transcript prevalidation onto real product paths while preserving
the legacy VS Code timeline as the default fallback.

- Add version-bound document HTML with strict CSP and schema checks
- Add opt-in ACP projection with stable source identity
- Reuse product export code across hosts and the integration runner
- Remove test-only production probes and consolidate shared validation

Note: Browser, host-action, VSIX, and packaging gates remain pending.
Keep the npm lockfile aligned with the VS Code and HTML export package
manifests added by the transcript consumer migration.

- Lock the VS Code Web Shell workspace dependency
- Lock Web Templates SDK, Web Shell, and React build dependencies
…plit-20260818

# Conflicts:
#	packages/acp-bridge/src/transcript-replay.ts
#	packages/sdk-typescript/scripts/build.js
#	packages/sdk-typescript/src/daemon/ui/transcript.ts
#	packages/web-shell/client/components/MessageList.tsx
#	packages/web-shell/client/components/messages/GoalStatusMessage.tsx
Resolve review findings across document export and the VS Code ACP
timeline while preserving default interactive and readonly semantics.

- Preserve stable text and non-text identity across live and replay
- Harden export projection, budgets, URL handling, CSP, and nonces
- Make document rendering complete, inert, and browser-validated
- Keep VS Code transcript scope, theme, copy, and flags reactive
- Restore fixture, schema, hash, and compatibility contract locks

Note: The overall gate remains failed pending VSIX, host-action, and
packaged-artifact evidence.
…eb-shell-adapter-before-split-20260818

# Conflicts:
#	packages/web-shell/client/components/MessageList.tsx
#	packages/web-shell/client/components/messages/AssistantMessage.tsx
#	packages/web-shell/client/components/messages/ToolGroup.tsx
Close the latest review findings across transcript identity, VS Code
rehydration, and the HTML export security boundary.

- Terminate discrete ACP segments and bind automatic turn provenance
- Reset VS Code transcript scopes across replay and reconnect lifecycles
- Align Markdown sanitization and envelope budgets with document rendering
- Run browser gates with Chromium and complete third-party notices
Reduce the shared transcript review surface while keeping the VS Code and
HTML Export consumers and their security gates intact.

- Merge VS Code feature state into the ACP transcript hook
- Collect source identity once and strip it at the compatibility boundary
- Remove test-only export adapters, gate reports, and duplicate helpers
- Keep shell output separated when stable producer segments change
- Mark third-party notices as generated review content
Close the latest review findings around document resource safety and
recorded-user replay consistency.

- Redact local home paths structurally without corrupting remote URLs
- Sanitize nested Markdown images inside otherwise safe links
- Advance merged segment provenance without duplicating separators
- Keep recorded-user stable IDs anchored to durable record identity
…plit-20260818

# Conflicts:
#	packages/cli/src/acp-integration/session/Session.ts
#	packages/web-shell/client/components/MessageList.tsx
#	packages/web-shell/client/components/WebShellTranscript.tsx
#	packages/web-shell/client/components/messages/ToolGroup.test.tsx
#	packages/web-shell/client/components/messages/ToolGroup.tsx
Keep MR2A focused on the document export pipeline while preserving the legacy VS Code timeline.

- Retain VS Code /export html as a real document consumer
- Move live transcript adapters, identity wiring, and feature flag to MR2B
- Remove the direct Web Shell dependency and restore baseline notices
- Keep candidate identity evidence in integration-only helpers
- Document the MR1, MR2A, and MR2B delivery boundaries
# Conflicts:
#	packages/web-shell/client/components/messages/ToolGroup.tsx
# Conflicts:
#	packages/web-shell/client/components/WebShellTranscript.tsx
#	packages/web-shell/client/components/messages/PlanMessage.test.tsx
#	packages/web-shell/client/components/messages/TodoView.tsx
#	packages/web-shell/client/components/messages/ToolGroup.tsx
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head 13fde70, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 12 scenario(s).

Qwen Code · serve A/B

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head e67c1a0. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

ℹ️ No screenshot changed against the PR base — but this PR edits 19 render-shaping files:

  • packages/web-shell/client/components/MessageList.module.css
  • packages/web-shell/client/components/MessageList.tsx
  • packages/web-shell/client/components/MessageTimestamp.tsx
  • packages/web-shell/client/components/WebShellTranscript.tsx
  • packages/web-shell/client/components/messages/AssistantMessage.tsx
  • packages/web-shell/client/components/messages/Markdown.tsx
  • packages/web-shell/client/components/messages/PlanExecutionView.tsx
  • packages/web-shell/client/components/messages/PlanMessage.module.css
  • …and 11 more.

Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to packages/web-shell/client/e2e/visuals/screenshots.spec.ts that seeds whatever state the UI is gated on; it then appears here as a head-only (NEW) capture.

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR! This is MR2A of the shared chat transcript design — a planned milestone, and it reads that way. Gate notes below.

Template ✓ — all required sections are present. One small nit: the English half omits the ### Evidence (Before & After) subheading that the Chinese half includes. Not blocking.

Problem: this is a planned feature milestone, not theoretical hardening. It implements the merged design doc docs/design/web-shell/chat-transcript-contract-prevalidation.md (§9.6 HTML Export pipeline, §12.2 MR2 steps 5–9), follows the already-merged prevalidation stage (#9388), and links issue #9387. Worth noting: #9387 was opened by the same author and still carries need-discussion / priority/P3 — the prevalidation stage got merged, but the issue thread itself never got an explicit maintainer green-light for the production migration.

Direction: aligned. The design doc — merged by a maintainer in #9388 on 2026-08-19 — prescribes exactly this split: export builder + document mode + HTML wiring + browser/security gates now, VS Code live timeline migration deferred to MR2B with the gate intentionally left at overall: "fail" and selectedVscodePath: null. The issue carries roadmap/export-data. Claude Code's changelog has no direct HTML-export entry, but transcript rendering is an actively maintained surface there — the area is relevant.

Size: large. 11,210 changed lines across 96 files and 7 packages (cli, core, sdk-typescript, acp-bridge, web-shell, web-templates, vscode-ide-companion) — of which ~6,101 are production logic (68 files), 5,000 are tests (27 files), and 109 are schema. For a feat PR there is no hard block, but this carries the maintainer-awareness flag at 500+ production lines and the large-PR advisory at 1000+: if any seam here can be split (e.g. document schema + projector separately from host wiring), two smaller PRs would review faster. Deferring MR2B already helps.

Approach: the scope matches the design doc's consumer-ordered plan — the centerpiece is the new closed ExportTranscriptDocumentV1 projector (~2,000 lines under packages/cli/src/ui/utils/export/), plus markdown document policy, the web-templates document entry, and the integration-runner rewrite (−894 lines converging onto the shared pipeline). Things worth thinking about: the CLI-only build path now also compiles webui/web-shell/web-templates (build-time cost on every fast lane), and four new runtime dependencies (unified, remark-parse, remark-gfm, remark-math) enter the CLI for the markdown policy. Both follow from the design, but they are real tradeoffs.

Risk: no matches on the revert-correlated high-risk paths. Elevated attention goes to two places instead: the shared web-shell components (also serving the live interactive shell) and the security boundary itself — CI evidence will matter, and the ubuntu lane is currently red (details in the review comment).

Moving on to code review. 🔍

中文说明

感谢贡献!这是共享 chat transcript 设计的 MR2A——一个有计划的里程碑,整体也是如此呈现的。

模板 ✓ — 所有必需章节齐全。小问题:英文部分缺少中文部分包含的 ### Evidence (Before & After) 小节。不构成阻塞。

问题:这是有计划的特性里程碑,不是理论性加固。它实现了已合并的设计文档 chat-transcript-contract-prevalidation.md(§9.6 HTML Export 管线、§12.2 MR2 第 5–9 步),继已合并的 prevalidation 阶段(#9388)之后,并关联 issue #9387。值得注意:#9387 由同一作者提出,仍带 need-discussion / priority/P3 标签——prevalidation 阶段已合并,但 issue 本身尚未得到维护者对生产迁移的明确认可。

方向:对齐。设计文档(由维护者在 #9388 于 2026-08-19 合并)恰好规定了这一拆分:先做 export builder + document mode + HTML wiring + 浏览器/安全门禁,VS Code live timeline 迁移推迟到 MR2B,门禁有意保持 overall: "fail"selectedVscodePath: null。issue 带有 roadmap/export-data 标签。claude-code 的 changelog 没有直接的 HTML export 条目,但 transcript 渲染在那里是活跃维护的领域——该方向是相关的。

规模:大。96 个文件、7 个包共 11,210 行改动,其中约 6,101 行为生产逻辑(68 个文件),5,000 行为测试(27 个文件),109 行为 schema。feat 类 PR 没有硬性阻断,但 500+ 生产行触发维护者关注、1000+ 触发大 PR 建议:如果存在可拆分的接缝(例如 document schema + projector 与宿主接线分开),两个更小的 PR 审起来更快。推迟 MR2B 已经有所缓解。

方案:范围符合设计文档按消费者排序的计划——核心是新的封闭式 ExportTranscriptDocumentV1 投影器(约 2,000 行,位于 packages/cli/src/ui/utils/export/),外加 markdown 文档策略、web-templates document 入口,以及 integration runner 重写(−894 行,收敛到共享管线)。值得思考:CLI-only 构建路径现在也要编译 webui/web-shell/web-templates(每个快速通道的构建时间成本),并且 CLI 新增四个运行时依赖(unifiedremark-parseremark-gfmremark-math)用于 markdown 策略。两者都源自设计,但都是真实的取舍。

风险:未命中与 revert 相关的高风险路径。需要重点关注两处:共享 web-shell 组件(同时服务于 live 交互界面)和安全边界本身——CI 证据很重要,而 ubuntu 通道当前是红的(详见审查评论)。

进入代码审查。🔍

Qwen Code · qwen3.8-max

Reviewed at 5f9470b974c2e60e33f0d3278566adf7a69669f1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code review

I read the security boundary in detail — it is the heart of this PR, and it holds up: per-kind Pick allowlists with assertNever exhaustiveness (a future block kind fails the build, not the user), forbidden-field scans (rawInput/rawOutput/sessionId/…), fail-closed validateStrict in core, then a second full re-validation of the finished document (semantic safety + consistency + budgets + schema). Opaque IDs, zeroed block timestamps, home-path redaction, URL-scheme allowlist, data-URI-only images (no SVG), CSP default-src 'none' with nonce-gated scripts, renderer-version binding, and a build that refuses to inline any <script sequence. Live-mode preservation is structural, not hopeful: document is a third render mode, adapter behavior is opt-in flagged (safeToolProjection, includeSourceIdentity, default off), and the MessageList change is an extraction of groupParallelAgents into a shared adapter. The pipeline reuses the SDK's canonical projector instead of re-replaying records, per design §9.6. No critical security or correctness holes found in the boundary itself.

What does need attention:

  1. CI is red on this head, and the failure is in code this PR restructured. The ubuntu lane fails one web-shell test (evidence below): MessageList — compact mode > keeps a folded single-agent summary separate from an approving agent — the expected msg-summary-t1 node never renders. That test exists unchanged on main and is not modified here, while the grouping logic it exercises is exactly what this PR extracted and retyped. Please fix it or demonstrate it is a flake with a re-run; as it stands it contradicts the "interactive/readonly behavior preserved" claim.
  2. The SDK preview path changed semantics beyond export. segmentId now gates block merging (user text gets \n separators across segments, shell blocks only merge on matching segment), and argsSummary generation now redacts sensitive keys — on the shared daemon preview path, so the live UI sees these too. The updated 520-line daemonUi suite pins the new behavior, but the PR describes itself as preserving runtime semantics; worth a maintainer confirming that is the intended scope.
  3. Build/runtime footprint tradeoffs (follow the design, but are real): --cli-only builds now compile webui + web-shell + web-templates, and four new CLI runtime deps (unified, remark-parse, remark-gfm, remark-math) serve the markdown policy. Minor: Mermaid renders now queue through one global promise even in non-document mode.
sequenceDiagram
    participant P1 as ChatRecord records
    participant P2 as Record export policy
    participant P3 as SDK canonical projector
    participant P4 as Document projector
    participant P5 as Gates
    participant P6 as CSP template
    participant P7 as Browser gate
    P1->>P2 keep visible record kinds only
    P2->>P3 project to transcript blocks
    P3->>P4 typed allowlist, opaque IDs, redaction, budgets
    P4->>P5 schema plus semantic re-validation, fail closed
    P5->>P6 inject escaped JSON under nonce CSP
    P6->>P7 zero network, canary, search copy print probes
Loading
Files changed (30 of 96 shown)
File What changed
packages/cli/src/ui/utils/export/export-transcript-document.ts Centerpiece: closed V1 projector with per-kind allowlists, budgets, opaque IDs, redaction, double assertion pass
packages/cli/src/ui/utils/export/export-transcript-document-v1.schema.json Schema moved from test fixtures into production and tightened
packages/cli/src/ui/utils/export/markdown-document-policy.ts remark-based markdown URL and image policy for document text
packages/cli/src/ui/utils/export/formatters/html.ts Document render entry: renderer version check, CSP nonce, script-data injection, legacy fallback
packages/cli/src/ui/utils/export/html-script-data.ts JSON-in-script escaping extracted to a shared helper
packages/cli/src/ui/commands/exportCommand.ts CLI /export html now passes original records into the new pipeline
packages/cli/src/serve/server/session-export.ts Serve API passes original records for the html format
packages/vscode-ide-companion/src/services/sessionExportService.ts VS Code /export html routes records into the shared pipeline
packages/core/src/utils/schemaValidator.ts Additive validateStrict: no coercion, no fail-open, no mutation
packages/sdk-typescript/src/daemon/ui/toolPreview.ts Typed todo and result previews, argsSummary sensitive-key redaction
packages/sdk-typescript/src/daemon/ui/transcript.ts segmentId propagation and changed merge rules on the shared reducer
packages/sdk-typescript/src/daemon/ui/normalizer.ts segmentId and resultPreview extraction from event metadata
packages/sdk-typescript/src/daemon/ui/types.ts Preview and identity type additions
packages/web-shell/client/transcriptRenderMode.ts Adds the document render mode value
packages/web-shell/client/components/WebShellTranscript.tsx Document-mode wiring: no virtualization, no collapse, basic tables
packages/web-shell/client/adapters/transcriptToMessages.ts Opt-in safeToolProjection and source identity flags, default off
packages/web-shell/client/adapters/parallelAgentGrouping.ts Parallel-agent grouping extracted here to be shared with document mode
packages/web-shell/client/components/MessageList.tsx Consumes the extracted grouping; display-item types change (source of the red test)
packages/web-shell/client/components/messages/Markdown.tsx Document-only Mermaid limits and timeout, data-URI-only images in document mode
packages/web-templates/src/export-html/build.mjs Second bundle entry; refuses script-sequence inlining; placeholder guards
packages/web-templates/src/export-html/src/document-index.html CSP default-src none with nonce slots, no remote resources
packages/web-templates/src/export-html/src/document-main.tsx Document renderer bootstrap bound to the renderer version
packages/acp-bridge/src/transcript-replay.ts Replay adjustments feeding the document fixtures
integration-tests/chat-transcript-contract.test.ts Contract gate updated, overall stays fail with null selected path
integration-tests/chat-transcript-document.test.ts New Chromium browser gate: zero network, CSP, canary, search, copy, print
integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.js Runner rewritten onto the shared pipeline, minus 894 lines
.github/workflows/ci.yml Dedicated browser-gate step; playwright install paths de-duplicated
.github/workflows/e2e.yml Browser gate excluded from sharded e2e lanes
scripts/build.js cli-only builds now also build webui, web-shell, web-templates
docs/design/web-shell/chat-transcript-contract-prevalidation.md Design doc status updated for MR2A
…and 66 more files unit tests, fixtures, capability matrix, CSS modules, package manifests

Test evidence (this PR's own CI — unattended run, no PR code executed here)

The PR's central lane is red on the reviewed commit. Test (ubuntu-latest, Node 22.x) fails in the web-shell suite: 1 failed / 4276 passed (4277)

FAIL components/MessageList.dom.test.tsx
  > MessageList — compact mode > keeps a folded single-agent summary separate from an approving agent
AssertionError: expected null not to be null
 ❯ components/MessageList.dom.test.tsx:736:11
    expect(container.querySelector('[data-testid="msg-summary-t1"]')).not.toBeNull();

I verified this test exists at line 718 of MessageList.dom.test.tsx on main and is not touched by this PR, while the grouping code it exercises is exactly what this PR extracted and retyped — so I classify it as PR-caused rather than infra noise (log body claims aside, the diff and check identity say otherwise). The job log ends with the runner terminating after the suite; the failure above is the exit cause. macOS and Windows unit lanes are skipped on PRs by design (#10059); the integration lane was skipped for this run. Twenty auxiliary lanes passed, including web-shell E2E Smoke, Serve A/B, Desktop Shell (ubuntu + windows), Dependency CVE audit, Secret scan (TruffleHog), and precheck-pr. Not independently verified here: the author's reviewer-test-plan claims (Chromium browser gate behavior, VSIX notices parity) — the browser gate produced no green signal in this commit's CI (the lane failed at the unit step).

Check Conclusion
Test (ubuntu-latest, Node 22.x) ❌ failure — 1/4277 web-shell tests: MessageList compact-mode summary
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
Serve A/B (ubuntu-latest, Node 22.x) ✅ success
Desktop Shell (ubuntu-22.04 / windows-2022) ✅ success
Dependency CVE audit / Secret scan / precheck-pr ✅ success
Test (macos-latest / windows-latest, Node 22.x) ⏭️ skipped (off PRs per #10059)
Integration Tests (CLI, No Sandbox) ⏭️ skipped

Sandboxed verification would settle the behavioural claims once CI is green: @qwen-code /verify — whether the new document pipeline is load-bearing (A/B: export HTML through the new projector vs the legacy path, and does the browser gate actually fail when the allowlist projection is removed) cannot be proven by the unit suite alone; @qwen-code /tmux — for the user-visible /export html TUI surface. The author has write access, so either lane can be triggered directly; until the red unit test is resolved neither would say much.

中文说明

代码审查:安全边界是本 PR 的核心,审查结论是站得住的——逐 kind 的 Pick 白名单加 assertNever 穷尽检查(未来新增 block 类型会在构建期失败而不是在用户侧失败)、禁止字段扫描、核心层的 fail-closed validateStrict,以及对最终文档的第二轮完整复验(语义安全 + 一致性 + 预算 + schema)。opaque ID、块时间戳归零、家目录路径脱敏、URL 协议白名单、仅 data-URI 图片(无 SVG)、CSP default-src 'none' + nonce 脚本、渲染器版本绑定、构建期拒绝内联任何 <script 序列。live 模式的保持是结构性的:document 是第三种渲染模式,适配器行为由 opt-in 开关控制(默认关闭),MessageList 的改动只是把 groupParallelAgents 抽取为共享适配器。管线复用了 SDK 的 canonical projector,符合设计 §9.6。边界本身未发现严重安全或正确性漏洞。

需要关注的:

  1. 该 head 上 CI 是红的,且失败点正是本 PR 重构的代码。 ubuntu 通道有一个 web-shell 测试失败(证据见下):期望的 msg-summary-t1 节点没有渲染。该测试在 main 上原样存在且本 PR 未修改,而它测试的分组逻辑正是本 PR 抽取并重新定义类型的部分。请修复,或用重跑证明是 flake;当前状态与"保持 interactive/readonly 行为"的声明相矛盾。
  2. SDK 预览路径的语义变化超出了 export 范围。 segmentId 现在控制块合并,argsSummary 生成现在会脱敏敏感键——这发生在共享的 daemon 预览路径上,live 界面同样可见。更新后的 520 行 daemonUi 测试固定了新行为,但 PR 自述为保持运行时时序,值得维护者确认这是否预期范围。
  3. 构建/运行时足迹取舍(符合设计但真实存在):--cli-only 构建现在也要编译 webui + web-shell + web-templates;CLI 新增四个运行时依赖用于 markdown 策略。次要:Mermaid 渲染在非 document 模式下也通过全局队列串行化。

测试证据(引用本 PR 自身 CI,本次为无人值守运行,未执行任何 PR 代码):核心通道 Test (ubuntu-latest, Node 22.x) 在被审提交上是红的——web-shell 套件 1 失败 / 4276 通过,失败测试在 main 上原样存在且未被本 PR 修改,判定为 PR 导致而非基础设施噪音。macOS/Windows 单元通道按 #10059 设计对 PR 关闭;二十条辅助通道通过(含 web-shell E2E Smoke、Serve A/B、Desktop Shell、CVE 审计、密钥扫描)。作者自述的浏览器门禁行为未在此独立验证——浏览器门禁挂在失败的单测步骤之后,本次提交未跑完。CI 绿后建议用 @qwen-code /verify(A/B 证明 document 管线真正承重)与 @qwen-code /tmux/export html TUI 面)做沙箱验证;作者有写权限,可直接触发。

Qwen Code · qwen3.8-max

Reviewed at 5f9470b974c2e60e33f0d3278566adf7a69669f1 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 2/5 — the security boundary is genuinely well built, but the PR's own CI fails a pre-existing test that pins the exact "live behavior preserved" claim it makes, and a change of this size needs a maintainer in the loop regardless.

Stepping back: my independent take on this problem would have been almost exactly what this PR does — a closed, versioned document type with per-kind allowlists, a one-way projector that never serializes raw payloads, reuse of the existing canonical projection, a nonce-CSP template with zero remote surface, and all hosts converged on one renderer. The implementation matches or exceeds that, and the defensive layers (forbidden-field scan, second full re-validation, assertNever exhaustiveness, build-time refusal to inline script sequences) are better than what I would have sketched. This is not volume-for-volume's-sake; it tracks a maintainer-merged design doc step by step.

But two things keep this from a clean pass:

  • The red CI is the core claim breaking. The failing test is unchanged from main and covers the compact-mode grouping this PR extracted and retyped. If the extraction were truly behavior-preserving, that test would pass. Either the extraction changed something subtle (my read: likely) or the test is flaky (prove it with a re-run). Until then, "interactive/readonly semantics preserved" is asserted, not demonstrated — and on a security-boundary PR, the compatibility claim carries the same weight as the boundary itself.
  • Scope for a human, not a gate. ~6,100 production lines across 7 packages, a shared SDK preview path whose semantics changed (segmentId merge rules, argsSummary redaction), and a CLI build/runtime footprint that grows even under --cli-only. Nothing here looks wrong on reading, but at this size "looks right on reading" is not the bar — the Stage 0 rule puts this in maintainer territory, and I am capping my confidence accordingly rather than approving on the strength of a clean-ish read.

Verdict: request changes on the red CI (fix or prove flake), and defer the rest to @yiliang114 — you merged the prevalidation stage (#9388) this builds on; the questions worth your eyes are the SDK preview-semantics scope and whether MR2A should land whole or split at the schema/projector seam. @water-in-stone strong work on the boundary itself; the blocker is narrow and concrete.

中文说明

置信度:2/5 — 安全边界构建得确实很好,但 PR 自身的 CI 在一个既有测试上失败,而该测试固定的正是本 PR 所声称的"保持 live 行为";且无论如何在这样规模的改动下都需要维护者介入。

独立来看,我对这个问题的方案与本 PR 几乎一致:封闭的、版本化的文档类型加逐 kind 白名单、永不序列化 raw payload 的单向投影器、复用现有 canonical 投影、nonce-CSP 模板且零远程资源、所有宿主收敛到同一渲染器。实现达到甚至超过了这一方案,防御层次(禁止字段扫描、第二轮完整复验、assertNever 穷尽、构建期拒绝内联脚本序列)比我预想的更好。这不是为了凑量,而是逐步遵循了维护者已合并的设计文档。

但有两点使其无法干净通过:

  • 红的 CI 恰好打破了核心声明。 失败的测试在 main 上原样存在,覆盖的正是本 PR 抽取并重新定义类型的 compact-mode 分组。如果抽取真的保持行为不变,该测试应当通过。要么抽取悄悄改变了什么(我的判断:很可能),要么测试是 flaky(请用重跑证明)。在此之前,"保持 interactive/readonly 语义"只是声明而非证明——对安全边界类 PR,兼容性声明与边界本身同等重要。
  • 规模属于人类判断而非门禁。 约 6,100 行生产逻辑横跨 7 个包,共享 SDK 预览路径的语义发生了变化(segmentId 合并规则、argsSummary 脱敏),CLI 的构建/运行时足迹即使在 --cli-only 下也在增长。读起来没有明显错误,但在这个规模下"读起来对"不是标准——Stage 0 规则把它划入维护者范围,我据此压低置信度而不是凭借尚可的阅读印象放行。

结论:就红的 CI 请求修改(修复或证明是 flake),其余交由 @yiliang114 定夺——您合并了本 PR 所基于的 prevalidation 阶段(#9388);值得您关注的是 SDK 预览语义的范围,以及 MR2A 应整体合入还是在 schema/投影器接缝处拆分。@water-in-stone 边界本身做得很好;阻塞点是窄而具体的。

Qwen Code · qwen3.8-max

Reviewed at 5f9470b974c2e60e33f0d3278566adf7a69669f1 · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on one concrete blocker: the ubuntu CI lane is red on this head — "MessageList — compact mode > keeps a folded single-agent summary separate from an approving agent" fails in components/MessageList.dom.test.tsx (expected the msg-summary-t1 node to render, got null). That test is unchanged from main, while the grouping logic it pins is exactly what this PR extracted and retyped. Please fix it or demonstrate it is a flake with a re-run.

Everything else is in my review comments above — the security boundary reads well; the open items for the maintainer are the SDK preview-semantics scope and the MR2A size/split question. See the stage comments for details. 🙏

qwen-code-ci-bot pushed a commit that referenced this pull request Aug 27, 2026
@water-in-stone

Copy link
Copy Markdown
Collaborator Author

Follow-up on the change-request review, Stage 2 triage, and Stage 3 triage on current PR head ef5d30fac1.

All 33 inline review threads now have an author reply and are resolved:

  • 31 findings were addressed with focused production or regression-test changes, including the export redaction/markdown/resource-budget boundary, typed preview correctness, background-agent terminal state, document renderer error handling, interactive Web Shell compatibility, and build-path scoping.
  • R1-14 and R1-15 were answered with an explicit boundary decision rather than represented as code fixes. The Chromium document gate remains a dedicated Playwright integration gate (4/4), and the no-access-key projection check remains an isolated integration boundary (151/151, with the focused contract gate 3/3). They were not added as ordinary workspace unit-test scripts because doing so would weaken their runtime/dependency isolation.
  • The earlier compact-mode regression remains fixed: folded synthetic summaries stay separate from approving agents.

Validation on this head:

  • npm run build: passed
  • npm run typecheck: passed
  • CLI export tests: 48/48
  • SDK daemon UI tests: 352/352
  • Web Shell focused/relevant tests: 413/413
  • VS Code export tests: 9/9
  • transcript contract gate: 3/3
  • Chromium document gate: 4/4
  • isolated no-access-key integration suite: 151/151 across 13 files

I left the historical CHANGES_REQUESTED reviews intact; they were not dismissed. The inline concerns have been replied to and resolved against the current published head.

@water-in-stone

Copy link
Copy Markdown
Collaborator Author

Final main synchronization update: PR head is now df02ec37e4, a normal merge of QwenLM/qwen-code main at d526612e92 on top of the reviewed/fixed head ef5d30fac1. The merge introduced no additional conflict resolution or PR-specific production changes.

I reran the relevant validation after this merge: repository build and typecheck passed; CLI export 54/54, SDK transcript/UI 391/391, Web Shell relevant tests 686/686 (including the compact-mode regression), VS Code export 9/9, transcript contract 3/3, Chromium document gate 4/4, and the isolated no-access-key integration gate 151/151 across 13 files all passed.

All 33 inline review threads remain replied to and resolved. The historical CHANGES_REQUESTED reviews remain intact and were not dismissed.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

Not explored to full depth (tool budget reached): chunk 23: none — no checks were cut short.; chunk 15: executing packages/cli/src/ui/utils/export/export-transcript-document.test.ts to confirm green at HEAD — the review worktree has no node_modules /dist and a ….

Not reviewed: reverse audit — stopped before round 3 by the review time budget.

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/e2e.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:

  • integration-tests/chat-transcript-contract.test.ts:225 — [review] Schema-closure lock misses the toolResultPreview union (mutation stays green)
  • integration-tests/chat-transcript-contract.test.ts:91 — [review] Fixture-tree lock is fail-open for symlink entries
  • docs/design/web-shell/chat-transcript-contract-prevalidation.md:690 — [review] Six bare MR2 references orphan gate obligations after the MR2A/MR2B split
  • integration-tests/chat-transcript-document.test.ts:6 — [review] Browser gate imports playwright as a phantom dependency (webui hoist only)
  • packages/web-shell/client/adapters/parallelAgentGrouping.ts:30 — [review] Summary-row exclusion hardcodes summary- instead of the shared summaryRunId predicate
  • packages/acp-bridge/src/transcript-replay.ts:1262 — [review] Unreachable shell_output/tool_output lane branch in transcriptSegmentLane
  • packages/web-templates/src/export-html/build.mjs:142 — [review] Post-transform <script guard is dead code; misleading safety model
  • packages/web-shell/client/components/MessageList.tsx:2093 — [review] data-source-block-ids written on every row but read nowhere; includeSourceIdentity hard-wired for it
  • packages/web-shell/client/adapters/transcriptToMessages.ts:480 — [review] sourceBlockIds missing on three block-backed pushes (mid_turn/vision_bridge)
中文说明

仅完成部分审查,审查缺口已披露。

未探索到全部深度(达到工具调用预算):chunk 23:none — no checks were cut short.;chunk 15:executing packages/cli/src/ui/utils/export/export-transcript-document.test.ts to confirm green at HEAD — the review worktree has no node_modules /dist and a …

未审查:反向审计——评审时间预算不足,未能开始第 3 轮。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/e2e.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 9 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.22.2)

Comment thread packages/cli/src/ui/utils/export/export-transcript-document.ts Outdated
Comment thread packages/cli/src/ui/utils/export/markdown-document-policy.ts Outdated
Comment thread packages/cli/src/ui/utils/export/export-transcript-document.ts
Comment thread packages/cli/src/ui/utils/export/export-transcript-document.ts Outdated
Comment thread packages/cli/src/ui/utils/export/markdown-document-policy.ts Outdated
Comment thread packages/web-shell/client/components/messages/ToolGroup.tsx Outdated
Comment thread packages/web-templates/src/index.ts
Comment thread .github/workflows/e2e.yml
Comment thread packages/cli/src/ui/utils/export/markdown-document-policy.ts Outdated
Comment thread .github/workflows/ci.yml

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • dead post-rewrite <script guard in packages/web-templates/src/export-html/build.mjs:142 — already reported on the PR (round-2 deferred list, review 5041220005)

Not explored to full depth (tool budget reached): "agent reverse-audit (round 1)": exhaustive enumeration of the document renderer's markdown surface via the ExportTranscriptBlockV1→WebShellTranscript adapter — specifically whether generic pr…; chunk 39: executing ToolGroup.test.tsx under vitest to confirm the new tests are green — the review worktree has no node_modules and a monorepo install + build exceed…; chunk 36: executing Markdown.mermaid.test.ts and Markdown.test.ts at HEAD (no node_modules in the review worktree; install+build exceeds chunk budget); chunk 22: none — the only unperformed check (live vitest run of the new test file) was impossible for environment reasons (no installed dependencies in the review workt…; chunk 37: none — I could not install/inspect mermaid's bundled .d.ts locally (no node_modules in the worktree), but resolved this via the lockfile version and mermaid….

Not reviewed: reverse audit — stopped before round 2 by the review time budget.

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/e2e.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

Deferred under the convergence posture (round 3, not a blocker) — recorded, not requested in this round:

  • packages/web-shell/client/components/messages/AssistantMessage.tsx:46 — [review] showFooter document-mode guard has no witness test
  • packages/cli/src/ui/utils/export/formatters/html.ts:147 — [review] CreateExportTranscriptDocumentOptions.title is a dead switch
  • .github/workflows/ci.yml:846 — [review] transcript-document browser gate wiring has no contract pin
  • integration-tests/chat-transcript-document.test.ts:551 — [review] renderer-side budget guards (schemaVersion/blocks/envelope) have no failing test
  • packages/cli/src/ui/utils/export/export-transcript-document.ts:1193 — [review] APPROVED_PERMISSION_TOKENS omits proceed_always-family approval outcomes
  • packages/web-shell/client/components/MessageList.module.css:248 — [review] dead document-mode CSS for .sessionTimelineViewport contradicts hideSessionTimeline
  • packages/web-shell/client/components/MessageTimestamp.tsx:44 — [review] MessageTimestamp document-mode guard has no witness test
  • packages/web-shell/client/components/messages/UserShellMessage.module.css:16 — [review] document mode de-clips the shell box but the command header keeps its ellipsis

Convergence: round 3 posted 3 inline comment(s), 2 of them reported for the first time; the previous round posted 12 (12 new). Findings keep coming back to the same files: packages/cli/src/ui/utils/export/markdown-document-policy.ts (findings in rounds 1, 2; 1 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 1)"exhaustive enumeration of the document renderer's markdown surface via the ExportTranscriptBlockV1→WebShellTranscript adapter — specifically whether generic pr…;chunk 39:executing ToolGroup.test.tsx under vitest to confirm the new tests are green — the review worktree has no node_modules and a monorepo install + build exceed…;chunk 36:executing Markdown.mermaid.test.ts and Markdown.test.ts at HEAD (no node_modules in the review worktree; install+build exceeds chunk budget);chunk 22:none — the only unperformed check (live vitest run of the new test file) was impossible for environment reasons (no installed dependencies in the review workt…;chunk 37:none — I could not install/inspect mermaid's bundled .d.ts locally (no node_modules in the worktree), but resolved this via the lockfile version and mermaid…

未审查:反向审计——评审时间预算不足,未能开始第 2 轮。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/e2e.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 8 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 3 轮发布了 3 条行内评论,其中 2 条是首次提出;上一轮发布了 12 条(其中 12 条首次提出)。发现反复回到同一批文件:packages/cli/src/ui/utils/export/markdown-document-policy.ts(第 1、2 轮已出过发现,本轮又有 1 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

— qwen3.8-max via Qwen Code /review (v0.22.2)

Comment thread packages/cli/src/ui/utils/export/export-transcript-document.ts Outdated
Comment thread packages/cli/src/ui/utils/export/markdown-document-policy.ts Outdated
Comment thread pr-10076-comment-evaluation.md Outdated

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this head against the round-2 findings — the fixes look real: block.files now emits a file_attachment_excluded diagnostic and marks content loss, label() re-runs safeLabel after home-path redaction, link destinations are wrapped in angle brackets, wasCancelled propagates through mergeToolCall, delete-only diffs render, both Playwright revisions install, and the complexity veto is fence-aware with emphasis-delimiter counting. Two inline items still block merge.

One more blocker that doesn't map to a single line: the web-shell E2E Smoke lane is red on this head, and the signature is not a flake — [data-web-shell-root] never renders on /session/:id, so multiple specs (interaction-chain-smoke, collapsed-groups-persist, ...) fail through all 3 retries. The new ExportTranscriptDocument browser gate passes in the same run, which points at the main Web Shell mount path rather than the new document renderer. Worth reproducing locally and root-causing before the next push — this PR touches App.tsx / MessageList / useMessages / the adapters, so it's the first suspect.

Non-blocking: the 9 deferred items disclosed in the review summary (schema-closure lock missing the toolResultPreview union, symlink fail-open fixture lock, playwright phantom dependency, unread data-source-block-ids, etc.) are worth a follow-up issue at merge time so they don't get dropped.

Comment thread pr-10076-comment-evaluation.md Outdated
const defaultRootDir = path.resolve(__dirname, '..');
const TEST_FILE_RE = /\.(test|spec)\.(d\.)?[mc]?[jt]s(\.map)?$/;
const DEFAULT_MAX_NPM_PACKAGE_UNPACKED_BYTES = 96 * 1024 * 1024;
const DEFAULT_MAX_NPM_PACKAGE_UNPACKED_BYTES = 128 * 1024 * 1024;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Decision needed] R2-7 is resolved here by raising the budget from 96 to 128 MiB (measured package is ~117.8 MB). That unblocks the release lane, but it makes the ~20 MB inlined document template permanent in the npm package. The reviewer's other option was slimming the template (lazy-load or compress). Either is defensible, but a maintainer should pick it explicitly in this PR rather than letting the bump be the default.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not resolved yet on PR head 64cc57a59c.

The current branch keeps the explicit 128 MiB budget, and the real packaging flow passes at ~117.8 MB. The alternative—compressing or lazy-loading the ~20 MB renderer—changes the packaging/runtime design. As requested, I am leaving this thread open until a maintainer explicitly chooses which direction this PR should take.

Harden the document export boundary after the latest review found
mixed-path and Markdown parser/scanner gaps.

- Normalize dot segments and mixed separators before home redaction
- Align Markdown preflight with fences, HTML comments, and delimiters
- Remove the internal review report from the tracked repository
@water-in-stone

Copy link
Copy Markdown
Collaborator Author

Follow-up on the round-3 review and maintainer review at current PR head 64cc57a59c.

  • Replied to all 17 open inline threads. Sixteen code/repository concerns are fixed and resolved; the 128 MiB package-budget vs renderer-slimming decision has a truthful status reply and remains open for explicit maintainer direction.
  • Closed the latest home-path and Markdown parser/scanner bypasses with focused regressions, and removed the internal review-evaluation artifact from the tracked repository.
  • Investigated the red Web Shell E2E Smoke job rather than changing the mount path: the CI trace shows 117 module requests failing together with net::ERR_NETWORK_CHANGED, with no React/page error. On the exact reviewed head, the single affected flow passed 1/1 locally, the CI-equivalent first batch passed 7/7 with seven workers, and the complete smoke suite passed 44/44. This points to the runner/network event rather than a deterministic App/MessageList regression; a CI rerun is the appropriate next verification.

Validation for the published fixes: CLI Export/Markdown 53/53, repository build and typecheck passed, transcript contract + Chromium document gate 7/7, and focused lint/format/diff checks passed.

The historical CHANGES_REQUESTED reviews remain intact and were not dismissed.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs.

# Conflicts:
#	packages/web-shell/client/adapters/transcriptToMessages.ts
#	packages/web-shell/client/components/messages/UserMessage.test.tsx

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially reviewed — gaps disclosed.

6 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • APPROVED_PERMISSION_TOKENS omits proceed_always-family approval outcomes — already reported (round-3 deferred list, review 5051592643)
  • Unreachable shell_output/tool_output lane branch in transcriptSegmentLane incl. parentToolCallId ''-vs-absent facet — already reported (round-2 deferred list, review 5041220005)
  • sourceBlockIds missing on the three block-backed pushes (mid_turn/vision_bridge) — already reported (round-2 deferred list, review 5041220005)
  • data-source-block-ids written on every row but read nowhere; includeSourceIdentity hard-wired for it — already reported (round-3 deferred list, review 5051592643)
  • Six bare MR2 references orphan gate obligations after the MR2A/MR2B split — already reported (round-2 deferred list, review 5041220005)
  • showFooter document-mode guard has no witness test (AssistantMessage.tsx:46) — already reported (round-3 deferred list, review 5051592643)

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): chunk 10: none — no checks were cut short.; "agent reverse-audit (round 1)": executing packages/web-shell transcriptToMessages.test.ts under vitest (needs workspace dist builds) — all 13 new tests verified by static trace only, not by a ….

Not reviewed: reverse audit — stopped before round 2 by the review time budget.

Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/e2e.yml: actionlint embedded-shell source mapping is not yet supported — not linted.

Deferred under the convergence posture (round 4, not a blocker) — recorded, not requested in this round:

  • packages/web-shell/client/components/artifacts/turnOutputSelectors.test.ts:794 — [review] write_file content/newText precedence unpinned — key-swap mutation survives the suite
  • integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.js:12 — [review] nothing anywhere exercises the rewritten concurrent-runner exporter
  • integration-tests/concurrent-runner/export-html-from-chatrecord-jsonl.js:12 — [review] full product pipeline under runner.py's fixed 30s timeout — in-budget session measured at 28.21s of the kill point
  • packages/cli/src/ui/utils/export/export-transcript-document.test.ts:855 — [review] OpaqueDocumentIds referential integrity unpinned — fresh-id-per-occurrence mutation keeps the suite green
  • packages/cli/src/ui/utils/export/export-transcript-document.ts:411 — [review] 5 of 7 VISIBLE_SYSTEM_RECORD_SUBTYPES entries are unreachable
  • packages/web-shell/client/components/artifacts/turnOutputSelectors.ts:389 — [review] safe-projection fallback covers only write_file — safe-projected edits render diff cards with no diffs

Convergence: round 4 posted 3 inline comment(s), 2 of them reported for the first time; the previous round posted 3 (2 new). Findings keep coming back to the same files: packages/cli/src/ui/utils/export/markdown-document-policy.ts (findings in round 2; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 6 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):chunk 10:none — no checks were cut short."agent reverse-audit (round 1)"executing packages/web-shell transcriptToMessages.test.ts under vitest (needs workspace dist builds) — all 13 new tests verified by static trace only, not by a …

未审查:反向审计——评审时间预算不足,未能开始第 2 轮。

未检查(工具限制,非阻断):the executable-script lint — .github/workflows/ci.yml: actionlint embedded-shell source mapping is not yet supported — not linted; the executable-script lint — .github/workflows/e2e.yml: actionlint embedded-shell source mapping is not yet supported — not linted。

收敛姿态下延后(第 4 轮,非阻断)——已记录,本轮不要求修改:共 6 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 4 轮发布了 3 条行内评论,其中 2 条是首次提出;上一轮发布了 3 条(其中 2 条首次提出)。发现反复回到同一批文件:packages/cli/src/ui/utils/export/markdown-document-policy.ts(第 2 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)

— qwen3.8-max via Qwen Code /review (v0.22.3)

Comment thread packages/cli/src/ui/utils/export/export-transcript-document.ts Outdated
Comment thread packages/cli/src/ui/utils/export/markdown-document-policy.ts Outdated
Comment thread packages/web-shell/client/adapters/transcriptToMessages.ts
heyang.why added 2 commits August 30, 2026 09:09
Close three Critical review findings in the document export and safe
transcript projection paths.

- Share percent-token decoding between home-path redaction and checks
- Reject parser/scanner divergence before Markdown parsing can stall
- Preserve todo entries carried by the document-safe projection
- Add regression witnesses for each reported failure mode
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review timed out. Qwen review timed out after 21600 seconds (of the 360-minute budget). This run already used the maximum 360 minute timeout. See workflow logs.

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Risk-directed review of 44fa755e1f: I traced the current HTML-export path from CLI/serve/VS Code through the safe transcript projection, document-mode Web Shell adapter, template injection/CSP, and the current check results. I found one new security-boundary blocker below.

The earlier release-lane failure is mechanically fixed by the 128 MiB package cap, but that choice permanently adds roughly 20 MiB to the shipped npm package (measured package ~117.8 MB). Please treat that as an explicit maintainer product decision, not as a neutral test-only adjustment; my recommendation is to slim/compress the embedded renderer if that can be kept within this PR.

CI has not produced a failing product assertion on this head: the main Qwen Code CI jobs were cancelled mid-run, and the automatic review job failed in review orchestration while attempting a 48-agent fan-out. Those runs therefore do not establish a green full gate.


function decodePercentToken(value: string): string {
let decoded = value;
for (let pass = 0; pass < 3; pass += 1) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Critical] Four percent-encoding layers still bypass the home-path boundary. decodePercentToken stops after exactly three passes, and both the redactor and the final residual validator call this same bounded decoder. For attacker-controlled transcript text, a four-times encoded path such as %2525252Fhome%2525252Falice%2525252Fprivate.txt is reduced after three passes only to %2Fhome%2Falice%2Fprivate.txt; no raw-home pattern matches, so the document is accepted and ships that token unchanged. One further ordinary URI decode by a recipient reveals /home/alice/private.txt. This is reachable through user/assistant/tool-result text and violates the PR's stated no-home-path export boundary.

Please make the bounded case fail closed: if the pass limit is reached while a token still contains recursively decodable escapes, redact or reject that token (or use a linear, token-length-bounded normalization that reaches a fixed point). Add a regression with a four-layer encoded Unix and Windows home path and assert that neither the encoded token nor the decoded username-bearing path survives.

@yiliang114

Copy link
Copy Markdown
Collaborator

HTML export UI regression report

Tested scope: PR #10076, current head e67c1a04ee2600339f670da2e80978eaf7fe1acf. The browser harness was prepared at 44fa755e1fe69a578fa7f27e5e3612fcb1df439c; before publishing this report I verified that the scoped export implementation and fixtures have no diff between that commit and the current head.

Method: I generated the legacy and WebShell HTML from the same representative ChatRecord fixture, exercised the PR's toHtml(sessionData, records) product path through a tmux-run helper, and opened the resulting standalone files in a real browser at 1440 px and 480 px widths. The richer block matrix used a schema-valid v1 document to isolate renderer coverage. I did not run npm ci or a broad local build.

Case matrix

ID Case Result Evidence
E01 Representative ChatRecord JSONL -> product HTML formatter -> file PASS tmux run wrote export.html and printed EXPORT_FILE_PRESENT
E02 Literal interactive TUI /export html entry NOT RUN The isolated worktree did not have a PR-built CLI and its pre-existing dependency tree did not match this branch. I did not install/build a second tree merely to re-prove command dispatch.
E03 Browser bootstrap and one-time rendering of the representative export PASS Each expected message/result appears exactly once; document width equals page scroll width
R01 User, assistant, thought, and basic read result PASS Product fixture plus v1 renderer probe
R02 Markdown headings, fenced code, table, inline math, and Mermaid PASS v1 renderer probe rendered KaTeX/Mermaid DOM and diagram SVG
R03 Read, command, and file-diff tool presentations PASS v1 renderer probe
R04 Todo/plan presentation PASS v1 renderer probe
R05 Permission history and resolved ask-user-question in read-only document mode PASS Content renders; action controls are disabled
R06 Subagent with nested tool evidence PASS v1 renderer probe
R07 User shell, context status, error, and prompt-cancelled states PASS v1 renderer probe
R08 Code-copy affordance PARTIAL Clicking changes the control to 已复制!; the in-app browser sandbox does not expose the OS clipboard payload for an independent read-back
L01 Desktop layout at 1440 px PASS No document-level horizontal overflow or hidden clipped descendants in the product fixture
L02 Narrow layout at 480 px PASS clientWidth === scrollWidth === 480; no hidden clipped descendants in the product fixture
L03 Long path and diff content PASS Content remains contained; any code-level scrolling does not create page-level overflow
L04 Partial/truncated export state PASS Partial export badge is visible while the recovered transcript remains readable
L05 Incompatible renderer version PASS Fails closed to a visible Unable to open this chat export error panel
O01 Standalone/offline document PASS Browser server log contains only the HTML document request; no subresource request was made
P01 Rich transcript coverage compared with legacy WebUI PASS / UPGRADE The new renderer covers thought, Mermaid/math, diff, plan, permission, ask-user, nested agent, shell, error, and cancellation states that the old export did not faithfully present
P02 Legacy session metadata panel FAIL / PARITY GAP Repository, branch, model, channel, prompt count, tokens, file operations, session ID, and export time are collected into document metadata but are not rendered by DocumentApp
P03 Context Usage visibility FAIL / PARITY GAP contextUsagePercent is serialized but is not rendered unless a separate transcript status block happens to contain it
P04 Theme parity FAIL / PARITY GAP The legacy page is dark/theme-aware; document mode currently hard-codes theme="light"
P05 Expand/collapse-all controls OPEN PRODUCT DECISION The legacy page exposes both controls; document mode renders the transcript expanded and omits them. This can be intentional for print/export, but it is not 1:1 parity.

Before / After — same representative fixture

Legacy WebUI exporter WebShell document exporter
Legacy desktop WebShell desktop

Responsive width — same fixture at 480 px

Legacy WebUI exporter WebShell document exporter
Legacy narrow WebShell narrow

Recovery and fail-closed states

Partial export Incompatible renderer version
Partial export Invalid renderer

Remaining blockers / decisions

  1. Critical: the unresolved four-layer percent-encoded home-path case can still survive export sanitization. This remains a merge blocker: feat: chat transcript mr2a html export #10076 (comment)
  2. Product parity: I would not describe the current result as 1:1 parity until the metadata panel, Context Usage, and theme behavior are either restored or explicitly accepted as intentional removals.
  3. Package budget: the unresolved package-size thread keeps the raised 128 MiB budget; the measured package is about 117.8 MiB. A maintainer still needs to accept that trade-off or request a renderer packaging change: feat: chat transcript mr2a html export #10076 (comment)
  4. Current CI snapshot: the main Node 22 test job and visual capture job pass on the latest head, but Java 11 is failing while Serve A/B, web-shell smoke, and automated review are still pending. Java 17 and Java 21 pass. Failed job: https://github.com/QwenLM/qwen-code/actions/runs/33311990001/job/99258596651
  5. Manual closeout: after the Critical and intended parity decisions are resolved, run one final interactive /export html command from a PR-built CLI and verify the produced file. That is the only user-facing command-path case not executed in this pass.

Conclusion: the WebShell document renderer is a functional upgrade for transcript content and its desktop/narrow standalone rendering is sound, but the PR is not ready to merge yet because of the unresolved security Critical and explicit legacy-parity losses above.

@yiliang114

Copy link
Copy Markdown
Collaborator

Follow-up on the product-direction items in the UI regression report: P02-P05 should no longer be treated as open decisions.

The migration should preserve existing useful export behavior unless there is a concrete incompatibility. Before merge, the WebShell document export should retain the legacy metadata summary (repository/branch/model/channel/tokens/session and file-operation information), Context Usage, theme behavior, and screen-level expand/collapse controls. Print CSS can still force everything expanded.

The boundary is also explicit: this is a static, read-only session-history export. It should not render a composer, send/attachment/voice controls, model or permission selectors, connect to the daemon, or expose actions that continue the conversation or execute tools. New document rendering capabilities such as Mermaid, math, typed tool output, diff, plan, ask-user history, and nested-agent history are welcome, but they should be additive rather than replacing legacy information.

Please keep these adaptations behind document mode so existing interactive/readonly WebShell consumers are unchanged, and render restored metadata only after the existing export sanitization policy has been applied. I consider the missing legacy behaviors in scope for this migration, separately from the unresolved security and CI blockers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants